import tushare as ts
import pandas as pd
import numpy as np
import math


class ResultAnalysis:
    initial_money = 100000  # 等待数据传入
    start_date = '2010年1月4日'
    end_date = '2019年12月31日'

    def __init__(self, initial=100000, start_date='2010-01-04', end_date='2019-12-31'):
        self.initial_money = initial
        self.start_date = start_date    # 可能需要将日期转换为统一格式
        self.end_date = end_date

    def get_initial_money(self):
        return self.initial_money

    def cal_gain_abs(self, return_list, charge=False):
        '''
        根据每日收益计算最后的总收益
        也可用于计算基准收益
        :param return_list:每日收益
        :return: 总收益
        '''
        final_money = return_list[-1]

        if charge:
            # 如果卖出，就要扣手续费
            selling_charge = final_money * 3e-4
            final_money -= selling_charge

        gain = final_money - self.initial_money

        return gain

    def cal_gain_ratio(self, return_list):
        '''
        根据每日收益计算最后的总收益率
        也可用于计算基准收益率
        :param return_list:每日收益
        :return: 总收益率
        '''
        return self.cal_gain_abs(return_list) / self.initial_money

    def cal_return_list_SSEC(self, charge=False):
        '''
        计算买入上证指数的每日资产，可考虑手续费（charge=Ture）
        :return: days:日期列表，return_list:每日收益列表
        '''
        SSEC = pd.read_csv('上证指数历史数据.csv')
        SSEC = SSEC[['日期', '收盘']]
        SSEC = SSEC.reindex(index=SSEC.index[::-1])
        SSEC = SSEC.reset_index(drop=True)

        start_price = float(SSEC.loc[0, '收盘'].replace(',', ''))
        end_price = float(SSEC.loc[SSEC.shape[0] - 1, '收盘'].replace(',', ''))
        start_index = 0
        end_index = SSEC.shape[0]
        for i in range(SSEC.shape[0]):
            if self.start_date <= SSEC.loc[i, '日期']:    # 考虑开始和结束时间不是交易日的情况
                start_price = float(SSEC.loc[i, '收盘'].replace(',', ''))
                start_index = i
                break
        for i in range(start_index, SSEC.shape[0]):
            if self.end_date >= SSEC.loc[i, '日期']:   # 考虑开始和结束时间不是交易日的情况
                end_price = float(SSEC.loc[i, '收盘'].replace(',', ''))
                end_index = i

        return_list = []
        days = []
        if charge:
            buying_charge = self.initial_money * 3e-4
            return_list.append(self.initial_money - buying_charge)
            holding = (self.initial_money - buying_charge) / start_price  # 持有份额
        else:
            return_list.append(self.initial_money)
            holding = self.initial_money / start_price
        days.append(SSEC.loc[start_index, '日期'])

        for i in range(start_index + 1, end_index + 1):
            days.append(SSEC.loc[i, '日期'])
            return_list.append(float(SSEC.loc[i, '收盘'].replace(',', '')) * holding)

        return days, return_list

    def cal_return_list_CSI300(self, charge=False):
        '''
        计算买入沪深300指数的每日资产
        :return: days:日期列表，return_list:每日收益列表
        '''
        CSI300 = pd.read_csv('沪深300指数历史数据.csv')
        CSI300 = CSI300[['日期', '收盘']]
        CSI300 = CSI300.reindex(index=CSI300.index[::-1])
        CSI300 = CSI300.reset_index(drop=True)

        # 根据需要 处理一下日期的格式

        start_price = float(CSI300.loc[0, '收盘'].replace(',', ''))
        end_price = float(CSI300.loc[CSI300.shape[0] - 1, '收盘'].replace(',', ''))
        start_index = 0
        end_index = CSI300.shape[0]
        for i in range(CSI300.shape[0]):
            if self.start_date <= CSI300.loc[i, '日期']:  # 考虑开始和结束时间不是交易日的情况
                start_price = float(CSI300.loc[i, '收盘'].replace(',', ''))
                start_index = i
                break
        for i in range(start_index, CSI300.shape[0]):
            if self.end_date >= CSI300.loc[i, '日期']:  # 考虑开始和结束时间不是交易日的情况
                end_price = float(CSI300.loc[i, '收盘'].replace(',', ''))
                end_index = i

        return_list = []
        days = []

        if charge:
            buying_charge = self.initial_money * 3e-4
            return_list.append(self.initial_money - buying_charge)
            holding = (self.initial_money - buying_charge) / start_price  # 持有份额
        else:
            return_list.append(self.initial_money)
            holding = self.initial_money / start_price
        days.append(CSI300.loc[start_index, '日期'])

        for i in range(start_index + 1, end_index + 1):
            days.append(CSI300.loc[i, '日期'])
            return_list.append(float(CSI300.loc[i, '收盘'].replace(',', '')) * holding)

        return days, return_list

    def cal_gain_list_SSEC(self):
        '''
        计算买入上证指数的每日收益
        :return: days:日期列表，gain_list:每日收益列表
        '''
        days, return_list = self.cal_return_list_SSEC()
        gain_list = []
        for i in range(len(return_list)):
            gain_list.append(return_list[i] - self.initial_money)

        return days, gain_list

    def cal_gain_list_CSI300(self):
        '''
        计算买入沪深300指数的每日收益
        :return: days:日期列表，gain_list:每日收益列表
        '''
        days, return_list = self.cal_return_list_CSI300()
        gain_list = []
        for i in range(len(return_list)):
            gain_list.append(return_list[i] - self.initial_money)

        return days, gain_list

    def cal_MaxDrawdown(self, return_list):
        '''
        计算最大回撤、最大回撤率和对应的回撤持续天数
        可理解为可能发生的最大亏损幅度，其值等于策略收益曲线上高点到后期最低点的回撤幅度的最大值。
        return_list：是每日资金的变化曲线
        np.maximum.accumulate(return_list)：找到return_list中的累计最大值，例如：
        d = np.array([2, 0, 3, -4, -2, 7, 9])
        c = np.maximum.accumulate(d)
        #c = array([2, 2, 3, 3, 3, 7, 9])
        i：为最大回撤截止的时间
        j：为最大回撤开始的时间
        drawdown_max：最大回撤
        drawdown_rate：最大回撤对应的回撤率
        drawdown_tian：回撤持续天数
        '''
        i = np.argmax((np.maximum.accumulate(return_list) - return_list))
        if i == 0:
            return 0
        j = np.argmax(return_list[:i])  # 开始位置
        drawdown_max = return_list[j] - return_list[i]
        drawdown_rate = (return_list[j] - return_list[i]) / return_list[j]
        drawdown_tian = i - j

        return drawdown_rate, drawdown_max, drawdown_tian, j, i

    def beat_benchmark_ratio(self, return_list):
        '''
        计算击败基准比率（日胜率）
        （当日策略收益跑赢当日基准收益的天数/总交易日天数）*100%
        统计日收益率打败基准收益的概率。数值越大越好，表示策略收益能力越强。
        :param return_list:
        :return: 击败基准比率（日胜率）
        '''
        return_list_SSEC = self.cal_return_list_SSEC()
        win_day = 0
        for i in range(len(return_list)):
            if return_list[i] > return_list_SSEC[i]:
                win_day += 1
        return win_day / len(return_list)

    def annualized_rate_of_return(self, days, return_list):
        '''
        计算年化收益率
        计算公式： (1 + 收益率) ^ (年平均交易日 / 策略执行总天数) - 1
        :param days: 策略执行的总日期列表
        :param return_list: 每日资金
        :return: 年化收益率
        '''
        num_days = len(days)
        trading_day_a_year = 243    # 根据计算，2010至2019年，每年平均交易日为243天
        return math.pow(1 + self.cal_gain_ratio(return_list), trading_day_a_year / num_days) - 1

    def cal_sharp_ratio(self, days, return_list):
        '''
        计算夏普比率
        :param days: 策略执行的总日期列表
        :param return_list: 每日资金
        :return: 夏普比率
        '''
        E_Rp = self.annualized_rate_of_return(days, return_list)    # 策略年化收益率
        Rf = 4e-2   # 根据资料，可假设国债的年化收益率是 4%
        num_days = len(days)    # 回测时间段内的所有交易日
        trading_day_a_year = 243  # 根据计算，2010至2019年，每年平均交易日为243天
        diff_return = np.diff(return_list)  # 每天相较于前一天的收益
        gain_ratio = diff_return / return_list[:-1]   # 每天相较于前一天的收益率
        sum_ratio2 = 0
        avg_gain_ratio = np.mean(gain_ratio)
        for i in gain_ratio:
            sum_ratio2 += math.pow(i - avg_gain_ratio, 2)
        sigma_p = math.sqrt(sum_ratio2 * trading_day_a_year / num_days)  # 每日收益率的标准差

        return (E_Rp - Rf) / sigma_p


if __name__ == '__main__':
    rs = ResultAnalysis(start_date='2013-04-12', end_date='2018-05-20')
    days, return_list = rs.cal_return_list_SSEC()
    print(len(days))
    print(days)
